The model layer in an object oriented application contains the business logic, that is the structure of the data and all calculations, validations and processes associated to this data.
OpenXava is a model oriented framework where the model is the most important, and the rest (e.g. user interface) depends on it.
The way to define the model in OpenXava is using XML and a few of Java. OpenXava generates a complete Java implementation of your model from your definition.
Java Implementation
Currently OpenXava generates code for the next 4 alternatives:
Plain Java Classes (the so-called POJOs) for the model using Hibernate for persistence.
Plain Java Classes for the model using EJB3 JPA (Java Persistence API) for persistence (new in v2.1).
Classic EJB2 EntityBeans for the model and persistence.
POJOs + Hibernate inside an EJB2 container.
The option 2 is the default one (new in v3.0) and the best for the most cases. The option 1 is also good, specially if you need to use Java 1.4. The option 3 is for supporting all the OpenXava applications written using EJB2 (EJB2 was the only option in OpenXava 1.x). The option 4 can be useful in some circumstances. You can see how to configure this in OpenXavaTest/properties/xava.properties.
Business Component
As you have seen the basic unit to create an OpenXava application is the business component. A business component is defined using a XML file. The structure of a business component in OpenXava is:
<?xmlversion="1.0"encoding="ISO-8859-1"?><!DOCTYPE component SYSTEM "dtds/component.dtd"><componentname="ComponentName"><!-- Model --><entity>...</entity><aggregatename=”...”>...</aggregate><aggregatename=”...”>...</aggregate>
...
<!-- View --><view>...</view><viewname="...">...</view><viewname="...">...</view>
...
<!-- Tabular data --><tab>...</tab><tabname=”...”>...</tab><tabname=”...”>...</tab>
...
<!-- Object relational mapping --><entity-mappingtable="...">...</entity-mapping><aggregate-mappingaggregate=”...” table="...">...</aggregate-mapping><aggregate-mappingaggregate=”...” table="...">...</aggregate-mapping>
...
</component>
The first part of the component, the part of entity and aggregates, is used to define the model. In this chapter you will learn the complete syntax of this part.
Entity and aggregates
The definition for entity and aggregate are practically identical. The entity is the main object that represents the business concept, while aggregates are additional object needed to define the business concept but cannot have its own life. For example, when you define an Invoice component, the heading data of invoice are in entity, while for invoice lines you can create an aggregate called InvoiceDetail; the life cycle of an invoice line is attached to the invoice, that is an invoice line without invoice has no meaning, and sharing an invoice line by various invoices is not possible, hence you will model InvoiceDetail as aggregate.
Formally, the relationship between A and B is aggregation, and B can be modeled as an aggregate when:
You can say that A has an B.
If A is deleted then its B is deleted too.
B is not shared.
Sometimes the same concept can be modeled as aggregate or as entity in another component. For example, the address concept. If the address is shared by various persons then you must use a reference to entity, while if each person has his own address maybe an aggregate is a good option.
bean (one, optional): Allows you to use an already existing JavaBean (a simple Java class, the so-called POJO). This applies if you use JPA o Hibernate as persistence engine. In this case the code generation for POJO and Hibernate mapping of this component will not be produced.
ejb (one, optional): Allows you to use an already existing EJB. This only applies if you use EJB CMP2 as persistence engine. In this case code generation for EJB code of this component will not be produced. It does not apply to EJB3.
implements (several, optional): The generated code will implement this interface.
property (several, optional): The properties represent Java properties (with its setters an getters) in the generated code.
reference (several, optional): References to other models, you can reference to the entity of another component or an aggregate of itself.
collection (several, optional): Collection of references. In the generated code it is a property that returns a java.util.Collection.
method (several, optional): Creates a method in the generated code, in this case the method logic is in a calculator (ICalculator).
finder (several, optional): Used to generate finder methods. Finder methods are static method located in the POJO class. In the case of EJB2 generation EJB2 finders are generated.
postcreate-calculator (several, optional): Logic to execute after making an object persistent. In Hibernate in a PreInsertEvent, in EJB2 in the ejbPostCreate method.
postload-calculator (several, optional): Logic to execute just after load the state of an object from persistent storage. In Hibernate in a PostLoadEvent, in EJB2 in the ejbLoad method.
postmodify-calculator (several, optional): Logic to execute after modifying a persistent object and before storing its state in persistent storage. In Hibernate in a PreUpdateEvent, in EJB2 in the ejbStore method.
preremove-calculator (several, optional): Logic to execute just before removing a persistent from persistent storage. In Hibernate in PreDeleteEvent, in EJB2 in the ejbRemove method.
validator (several, optional): Executes a validation at model level. This validator can receive the value of various model properties. To validate a single property it is better to use a property level validator.
remove-validator (several, optional): It's executed before removal, and can deny the object removing.
Bean
With <bean/> you can specify that you want to use your own Java class.
For example:
If you want a reference from the OpenXava generated code to your own handwritten code, then your Java class has to implement an interface (IFamily in this case) that extends IModel (see org.openxava.test.Family in OpenXavaTest/src).
Additionally you have to define the mapping using Hibernate:
<?xmlversion="1.0"?><!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mappingpackage="org.openxava.test.model"><classname="Family"table="XAVATEST@separator@FAMILY"><idname="oid"column="OID"access="field"><generatorclass="uuid"/></id><propertyname="number"column="NUMBER"/><propertyname="description"column="DESCRIPTION"/></class></hibernate-mapping>
You can put this file in the hibernate folder of your project. Moreover in this folder you have the hibernate.cfg.xml file that you have to edit in this way:
In this easy way you can wrap your existing Java and Hibernate code with OpenXava. Of course, if you are creating a new system it is much better to rely on the OpenXava code generation.
EJB (2)
With <ejb/> you can specify that you want to use your own EJB (1.1 and 2.x version).
For example:
In this simple way you can write you own EJB code instead of using code that OpenXava generates.
You can write the EJB code from scratch (only for genuine men), if you are a normal programmer (hence lazy) probably you prefer to use wizards, or better yet XDoclet. If you choose to use XDoclet, then you can put your own XDoclet classes in the package model (or another package of your choice. This depends on the value of the model.package variable in build.xml) in src folder of your project.; and your XDoclet code will be generated with the rest of OpenXava code.
For our example you can write a FamilyBean class in this way:
And you can write your Java interface in this way:
packageorg.openxava.test.model;importjava.rmi.*;/**
* @author Javier Paniza
*/publicinterface IWithName {String getName()throwsRemoteException;}
Beware to make that generated code implements your interface. In this case you have a property named name that generates a method called getName() that implements the interface.
In your generated code you can find an ICustomer interface:
This pithy feature makes the polymorphism a privileged guest of OpenXava.
As you can see OpenXava generates an interface for each component. It's good that in your code you use these interfaces instead of POJO classes or EJB2 remote interfaces. All code made in this way can be used with POJO and EJB2 version on same time, or allows you to migrate from a EJB2 to a POJO version with little effort. Although, if you are using POJOs exclusively you may use the POJOs classes directly and ignore the interfaces, as you wish.
Property (4)
An OpenXava property corresponds exactly to a Java property. It represents the state of an object that can be read and in some cases updated. The object does not have the obligation to store physically the property data, it only must return it when required.
The syntax to define a property is:
<propertyname="propertyName" <!-- 1 -->
label="label" <!-- 2 -->
type="type" <!-- 3 -->
stereotype="STEREOTYPE" <!-- 4 -->
size="size" <!-- 5 -->
scale="scale" <!-- 6 new in v2.0.4 -->
required="true|false <!-- 7 -->
key="true|false" <!-- 8 -->
hidden="true|false" <!-- 9 -->
search-key="true|false" <!-- 10 new in v2.2.4 -->
version="true|false" <!-- 11 new in v2.2.3 -->
>
<valid-values .../><!-- 12 --><calculator .../><!-- 13 --><default-value-calculator .../><!-- 14 --><calculation .../><!-- 15 new in v5.7 --><validator .../> .... <!-- 16 --></property>
name (required): The property name in Java, therefore it must follow the Java convention for property names, like starting with lower-case. Using underline (_) is not advisable.
label (optional): Label showed to the final user. Is much better use the i18n files.
type (optional): It matches with a Java type. All types valid for a Java property are valid here, this include classes defined by you. You only need to provide a converter to allow saving in database and a editor to render as HTML; thus that things like java.sql.Connection or so can be a little complicated to manage as a property, but not impossible. It's optional, but only if you have specified <bean/> or <ejb/> or this property has a stereotype with a associated type.
stereotype (optional): Allows to specify an special behavior for some properties.
size (optional): Length in characters of property. Useful to generate user interfaces. If you do not specify the size, then a default value is assumed. This default value is associated to the stereotype or type and is obtained from default-size.xml.
scale (optional): (new in v2.0.4) Scale (size of decimal part) of property. Only applies to numeric properties. If you do not specify the scale, then a default value is assumed. This default value is associated to the stereotype or type and is obtained from default-size.xml.
required (optional): Indicates if this property is required. By default this is true for key properties hidden (new in v2.1.3) or without default value calculator on create and false in all other cases. On saving OpenXava verifies if the required properties are present. If this is not the case, then saving is not done and a validation error list is returned. The logic to determine if a property is present or not can be configured by creating a file called validators.xml in your project. You can see the syntax in OpenXava/xava/validators.xml.
key (optional): Indicates that this property is part of the key. At least one property (or reference) must be key. The combination of key properties (and key references) must be mapped to a group of database columns that do not have duplicate values, typically the primary key.
hidden (optional): A hidden property has a meaning for the developer but not for the user. The hidden properties are excluded when the automatic user interface is generated. However at Java code level they are present and fully functional. Even if you put it explicitly into a view the property will be shown in the user interface.
search-key (optional): (new in v2.2.4) The search key properties are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the key (key=”true”) properties for searching by default, and if the key (key=”true”) properties are hidden then it uses the first property in the view. With search-key you can choose explicitly the properties for searching.
version (optional): (new in v2.2.3) A version property is used for optimistic concurrency control. If you want control concurrency you only need to have a property marked as version=”true” in your component. Only a single version property should be used per component. The following types are supported for version properties: int, Integer, short, Short, long, Long, Timestamp. The version properties are considered hidden.
valid-values (one, optional): To indicate that this property only can have a limited set of valid values.
calculator (one, optional): Implements the logic for a calculated property. A calculated property only has getter and is not stored in database.
default-value-calculator (one, optional): Implements the logic to calculate the default (initial) value for this property. A property with default-value-calculator has setter and it is persistent.
calculation (one, optional): (new in v5.7) Arithmetic expression to do the calculation for the property. The calculation is done in the user interface when any operand changes.
validator (several, optional): Implements the validation logic to execute on this property before modifying or creating the object that contains it.
Stereotype
A stereotype is the way to determine a specific behavior of a type. For example, a name, a comment, a description, etc. all correspond to the Java type java.lang.String but you surely wish validators, default sizes, visual editors, etc. different in each case and you need to tune finer; you can do this assigning a stereotype to each case. That is, you can have the next sterotypes NAME, MEMO or DESCRIPTION and assign them to your properties.
OpenXava comes with these generic stereotypes:
DINERO, MONEY
FOTO, PHOTO, IMAGEN, IMAGE
TEXTO_GRANDE, MEMO, TEXT_AREA
ETIQUETA, LABEL
ETIQUETA_NEGRITA, BOLD_LABEL
HORA, TIME
FECHAHORA, DATETIME
GALERIA_IMAGENES, IMAGES_GALLERY new in v2.0
RELLENADO_CON_CEROS, ZEROS_FILLED new in v2.0.2
TEXTO_HTML, HTML_TEXT (text with editable format) new in v2.0.3
IMAGE_LABEL, ETIQUETA_IMAGEN (image depending on property content) new in v2.1.5
EMAIL new in 2.2.3
TELEFONO, TELEPHONE new in 2.2.3
WEBURL new in 2.2.3
IP new in 2.2.4
ISBN new in 2.2.4
TARJETA_CREDITO, CREDIT_CARD new in 2.2.4
LISTA_EMAIL, EMAIL_LIST new in 2.2.4
Now you will learn how to define your own stereotype. You will create one called PERSON_NAME to represent names of persons.
Edit (or create) the file editors.xml in your folder xava. And add:
This way you define the editor to render for editing and displaying properties of stereotype PERSON_NAME.
Also you can edit stereotype-type-default.xml and the line:
<forstereotype="PERSON_NAME"type="String"/>
Furthermore it is useful to indicate the default size; you can do this by editing default-size.xml of your project:
<for-stereotypename="PERSON_NAME"size="40"/>
Thus, if you do not put the size in a property of type PERSON_NAME a value of 40 is assumed.
Not so common is changing the validator for required, but if you wish to change it you can do it adding to validators.xml of your project the next definition:
In this case a value of 40 is assumed as size, String as type and the NotBlankCharacterValidator validator is executed to verify if it is required.
IMAGES_GALLERY stereotype (new in v2.0)
If you want that a property of your component hold a gallery of images. You only have to declare your property with the IMAGES_GALLERY stereotype, in this way:
Furthermore, in the mapping part you have to map your property to a table column suitable to store a String with a length of 32 characters (VARCHAR(32)).
And everything is done.
In order to support this stereotype you need to setup the system appropriately for your application.
First, create a table in your database to store the images:
CREATETABLE IMAGES (
ID VARCHAR(32)NOTNULLPRIMARYKEY,
GALLERY VARCHAR(32)NOTNULL,
IMAGE BLOB);
CREATEINDEX IMAGES01
ON IMAGES (GALLERY);
The type of IMAGE column can be a more suitable one for your database to store byte [] (for example LONGVARBINARY) .
The name of the table is arbitrary. You need to specify the table name and schema (new in v2.2.4) name in your configuration file (a .properties file in the root of your OpenXava project). In this way:
images.schema=MYSCHEMA
images.table=IMAGES
If you want to work without schema (new in v3.0.2) you need to put the property image.schema.definition of your configuration to empty string, as following:
images.schema.definition=
And finally you need to define the mapping in your hibernate/hibernate.cfg.xml file, thus:
After this you can use the IMAGES_GALLERY stereotype in all components of your application.
Concurrency and version property (new in v2.2.3)
Concurrency is the ability of the application to allow several users to save data at same time without losing data. OpenXava uses an optimistic concurrency schema. Using optimistic concurrency the records are not locked allowing high concurrency without losing data integrity.
For example, if a user A read a record and then a user B read the same record, modify it and save the changes, when the user A try to save the record he receives an error, then he need to refresh the data and retry his modification.
For activating concurrency support for an OpenXava component you only need to declare a property using version=”true”, in this way:
<propertyname="version"type="int"version="true"/>
This property is for use of persistence engine (Hibernate or JPA), your application or your user must not use this property directly.
Valid values
The element <valid-values/> allows you to define a property that can hold one of the indicated values only. Something like a C (or Java 5) enum.
It's easy to use, let's see this example:
The distance property only can take the following values: local, national or international, and as you have not put required=”true” the blank value is allowed too. The type is not necessary, int is assumed.
At user interface level the current implementation uses a combo. The label for each value is obtained from the i18n files.
At Java generated code level creates a distance property of type int that can take the values 0 (no value), 1 (local), 2 (national) o 3 (international).
At database level the value is by default saved as an integer, but you can configure easily to use another type and work with no problem with legate databases. See more at object/relational mapping chapter.
Calculator
A calculator implements the logic to execute when the getter method of a calculated property is called. The calculated properties are read only (only have getter) and not persistent (they do not match with any column of database table).
A calculated property is defined in this way:
Now when you (or OpenXava to fill the user interface) call to getUnitPriceInPesetas() the system executes EurosToPesetasCalculator calculator, but before this it sets the value of the property euros of EurosToPesetasCalculator with the value obtained from unitPrice of the current object.
Seeing the calculator code may be instructive:
You can notice two things, first (1) a calculator must implement org.openxava.calculators.ICalculator, and (2) the method calculate() executes the logic to generate the value returned by the property.
According to the above definitions now you can use the generated code in this way:
Product product = ...
product.setUnitPrice(2);BigDecimal result = product.getUnitPriceInPesetas();
And result will hold 332.772.
You can define a calculator without set from to define values for properties, as shown below:
In this case the property year and number of DetailsCountCalculator calculator are filled from properties of same name from the current object.
The from attribute supports qualified properties, as following:
The property string4 (1) of the calculator is filled using the value of name of the state (that is a reference), this a qualified property (reference.property). In the case of int5 (2) you can see that customer reference is not declared in Address, because it is referenced from the Customer entity, therefore Address has an implicit reference to its container model (its parent) that you can use in from attribute (new in v2.0.4). That is, the int5 property is filled with the number of the customer which has this address.
Also it's possible to assign a constant value to a calculator property:
In this case the property separator of ConcatCalculator has a constant value.
Another interesting feature of calculator is that you can access from it to the model object (entity or aggregate) that contains the property that is being calculated:
This calculator implements IModelCalculator (1) (new in v2.0) and to do this it has a method setModel (2), this method is called before calling the calculate() method and thus allows access to the model object (in this case an invoice) that contains the property inside calculate().
Within the code generated by OpenXava you can find an interface for each business concept that is implemented by the POJO class, the EJB2 remote interface and the EJB2 Bean class. That is for Invoice you have a IInvoice interface implemented by Invoice (POJO class), InvoiceRemote (EJB2 remote interface) and InvoiceBean (EJB2 bean class), this last two only if you generate EJB2 code. In the calculator of type IModelCalculator it is advisable to cast to this interface, because in this cases the same calculator works with POJOs, EJB2 remote interface and EJB2 bean class. If you are developing with a POJO only version (maybe the normal case) you can cast directly to the POJO class, in this case Invoice.
This calculator type is less reusable than that which receives simple properties, but sometimes are useful. Why is it less reusable? For example, if you use IInvoice to calculate a discount, this calculator only could be applied to invoices, but if you uses a calculator that receives amount and discountPercentage as simple properties this last calculator could be applied to invoices, deliveries, orders, etc.
From a calculator you have direct access to JDBC connections, here is an example:
To use JDBC your calculator must implement IJDBCCalculator (1) and then it will receive a IConnectionProvider (2) that you can use within calculate(). Yes, the JDBC code is ugly and awkward, but sometime it can help to solve performance problems.
The calculators allow you to insert your custom logic in a system where all code is generated; and as you see it promotes the creation of reusable code because the calculators nature (simple and configurable) allows you to use them time after time to define calculated properties and methods. This philosophy, simple and configurable classes that can be plugged in several places is the cornerstone that sustains all OpenXava framework.
OpenXava comes with a set of predefined calculators, you can find them in org.openxava.calculators.
Default value calculator
With <default-value-calculator/> you can associate logic to a property, but in this case the property is readable, writable and persistent. This calculator is for calculating its initial value. For example:
In this case when the user tries to create a new Invoice (for example) he will find that the year field already has a value, that he can change if he wants to do.
You can indicate that the value will be calculated just before creating (inserting into database) an object for the first time; this is done this way:
If you use on-create=”true” then you will obtain that effect.
A typical use of the on-create=”true” is for generating identifiers automatically. In the above example, an unique identifier of type String and 32 characters is generated. Also you can use other generation techniques, for example, a database sequence can be defined in this way:
SequenceCalculator(new in v2.0.1) and IdentityCalculator (new in v2.0.2) do not work with EJB2. They work with Hibernate and EJB3.
If you define a hidden key property with no default calculator with on-create=”true” then it uses identity, sequence or hilo techniques automatically depending upon the capabilities of the underlying database. As this:
The technique to configure the validator (with <set/>) is exactly the same than in calculators. With the attribute only-on-create="true" you can define that the validation will be executed only when the object is created, and not when it is modified.
The validator code is:
A validator has to implement IPropertyValidator (1), this obliges to the calculator to have a validate() method where the validation of property is executed. The arguments of validate() method are:
(2) Messages errors: A object of type Messages that represents a set of messages (like a smart collection) and where you can add the validation errors that you find.
(3) Object value: The value to validate.
(4) String objectName: Object name of the container of the property to validate. Useful to use in error messages.
(5) String propertyName: Name of the property to validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with errors.add()) by sending a message identifier and the arguments. If you want to obtain a significant message you need to add to your i18n file the next entry:
exclude_string={0} cannot contain {2} in {1}
If the identifier sent is not found in the resource file, this identifier is shown as is; but the recommended way is always to use identifiers of resource files.
The validation is successful if no messages are added and fails if messages are added. OpenXava collects all messages of all validators before saving and if there are messages, then it display them and does not save the object.
The package org.openxava.validators contains some common validators.
Default validator (new in v2.0.3)
You can define a default validator for properties depending of its type or stereotype. In order to do it you have to use the file xava/validators.xml of your project to define in it the default validators.
For example, you can define in your xava/validators.xml the following:
This property will be validated using PersonNameValidator although the property itself does not define any validator. PersonNameValidator is applied to all properties with PERSON_NAME stereotype.
You can also assign a default validator to a type.
In validators.xml files you can also define the validators for determine if a required value is present (executed when you use required="true"). Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining OpenXava/xava/validators.xml and OpenXavaTest/xava/validators.xml.
Calculation (new in v5.7)
With <calculation/> you can define an arithmetic expression to do the calculation for the property. The expression can contain +, -, *, (), numeric values and properties names of the same entity. For example:
Note as worker.hourPrice is used to get the value from the reference.
The calculation is executed and displayed when the user changes any value of the properties used in the expression in the user interface, however the value is not saved until the user clicks on save button.
Reference (5)
A reference allows access from an entity or an aggregate to another entity or aggregate. A reference is translated to Java code as a property (with its getter and its setter) whose type is the referenced model Java type. For example a Customer can have a reference to his Seller, and that allows you to write code like this:
name (optional, required if model is not specified): The name of reference in Java, hence must follow the rules to name members in Java, including start by lower-case. If you do not specify name the model name with the first letter in lower-case is assumed. Using underline (_) is not advisable.
label (optional): Label shown to the final user. It's much better use i18n.
model (optional, required if name is not specified): The model name to reference. It can be the name of another component, in which case it is a reference to entity, or the name of a aggregate of the current component. If you do not specify model the reference name with the first letter in upper-case is assumed.
required (optional): Indicates if the reference is required. When saving OpenXava verifies if the required references are present, if not the saving is aborted and a list of validation errors is returned.
key (optional): Indicates if the reference is part of the key. The combination of key properties and reference properties should map to a group of database columns with unique values, typically the primary key.
search-key (optional): (New in v3.0.2) The search key references are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the key (key=”true”) members for searching by default, and if the key (key=”true”) members are hidden then it uses the first property in the view. With search-key you can choose explicitly references for searching.
role (optional): Used only in references within collections. See below.
default-value-calculator (one, optional): Implements the logic for calculating the initial value of the reference. This calculator must return the key value, that can be a simple value (only if the key of referenced object is simple) or key object (a special object that wraps the key and is generated by OpenXava).
In a reference <defaut-value-calculator/> works like in a property, only that it has to return the value of the reference key, and on-create="true" is not allowed.
For example, in the case of a reference with simple key, you can write:
packageorg.openxava.test.calculators;importorg.openxava.calculators.*;importorg.openxava.test.ejb.*;/**
* @author Javier Paniza
*/publicclass DefaultWarehouseCalculator implements ICalculator {publicObject calculate()throwsException{
Warehouse key = new Warehouse();
key.setNumber(4);
key.setZoneNumber(4);return key;// This works with POJO and EJB2// return new WarehouseKey(new Integer(4), 4); // This only work with EJB2}}
Returns an object of type Warehouse, (or WarehouseKey if you use only EJB2).
Collection (6)
With <collection/> you define a collection of references to entities or aggregates. This is translated to Java as a property of type java.util.Collection.
Here syntax for collection:
name (required): The collection name in Java, therefore it must follow the rules for name members in Java, including starting with lower-case. Using underline (_) is not advisable.
label (optional): Label shown to final user. Is much better to use i18n files.
minimum (optional): Minimum number of expected elements. This is validated just before saving.
maximum (optional): (new v2.0.3) Maximum number of expected elements.
reference (required): With the syntax you can see in the previous point.
condition (optional): Restricts the elements that appear in the collection.
order (optional): The elements in collections will be in the indicated order.
calculator (optional): Allows you to define your own logic to generate the collection. If you use this, then you cannot use neither condition nor order.
postremove-calculator (optional): Execute your custom logic just after an element is removed from collection.
Let's have a look at some examples. First a simple one:
If you have this within an Invoice, then you are defining a deliveries collection associated to that Invoice. The details to make the relationship are defined in the object/relational mapping.
Now you can write a code like this:
In this case you have a collection of aggregates, the details (or lines) of the invoice. The main difference between collection of entities and collection of aggregates is when you remove the main entity; in the case of a collection of aggregates its elements are deleted too. That is when you delete an invoice its details are deleted too.
The restriction minimum="1" requires at least one detail for the invoice to be valid.
With order you force that the details will be returned ordered by serviceType.
With postremove-calculator you indicate the logic to execute just after a invoice detail is removed.
As you see this is a conventional calculator as it is used in calculated properties. A thing to consider is that the calculator is applied to the container entity (in this case Invoice) and not to the collection element. That is, if your calculator implements IModelCalculator then it receives an Invoice and not an InvoiceDetail. This is consistent because it is executed after the detail is removed and the detail doesn't exist any more.
You have full freedom to define how the collection data is obtained, with condition you can overwrite the default condition generated by OpenXava:
<!-- Others carriers of same warehouse --><collectionname="fellowCarriers"><referencemodel="Carrier"/><condition>
${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND
${warehouse.number} = ${this.warehouse.number} AND
NOT (${number} = ${this.number})
</condition></collection>
If you have this collection within Carrier, you can obtain with this collection all carriers of the same warehouse but not himself, that is the list of his fellow workers. As you see you can use this in the condition in order to reference the value of a property of current object.
If with this you have not enough, you can write the logic that returns the collection. The previous example can be written in the following way too:
<!--The same that 'fellowCarriers' but implemented with a calculator--><collectionname="fellowCarriersCalculated"><referencemodel="Carrier"/><calculatorclass="org.openxava.test.calculators.FellowCarriersCalculator"/></collection>
And here the calculator code:
packageorg.openxava.test.calculators;importjava.rmi.*;importorg.openxava.calculators.*;importorg.openxava.test.ejb.*;/**
* @author Javier Paniza
*/publicclass FellowCarriersCalculator implements IModelCalculator {private ICarrier carrier;publicObject calculate()throwsException{// Using Hibernateint warehouseZoneNumber = carrier.getWarehouse().getZoneNumber();int warehouseNumber = carrier.getWarehouse().getNumber();
Session session = XHibernate.getSession();Query query = session.createQuery("from Carrier as o where " +
"o.warehouse.zoneNumber = :warehouseZone AND " +
"o.warehouse.number = :warehouseNumber AND " +
"NOT (o.number = :number)");
query.setInteger("warehouseZone", warehouseZoneNumber);
query.setInteger("warehouseNumber", warehouseNumber);
query.setInteger("number", carrier.getNumber());return query.list();/* Using EJB3 JPA
EntityManager manager = XPersistence.getManager();
Query query = manager.createQuery("from Carrier c where " +
"c.warehouse.zone = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZone());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());
return query.getResultList();
*//* Using EJB2
return CarrierUtil.getHome().findFellowCarriersOfCarrier(
carrier.getWarehouseKey().getZoneNumber(),
carrier.getWarehouseKey().get_Number(),
new Integer(carrier.getNumber())
);
*/}publicvoid setModel(Object model)throwsRemoteException{
carrier = (ICarrier) model;}}
As you see this is a conventional calculator. Obviously it must return a java.util.Collection whose elements are of type ICarrier.
The references in collections are bidirectional, this means that if in a Seller you have a customers collection, then in Customer you must have a reference to Seller. But if in Customer you have more than one reference to Seller (for example, seller and alternateSeller) OpenXava does not know which to choose, for this case you have the attribute role of reference. You can use it in this way:
To indicate that the reference seller and not alternateSeller will be used in this collection.
In the case of a collection of entity references you have to define the reference at the other side, but in the case of a collection of aggregate references this is not necessary, because in the aggregates a reference to this container is automatically generated.
Method (7)
With <method/> you can define a method that will be included in the generated code as a Java method.
The syntax for method is:
In this case you can notice that in arguments and exceptions the Java format is used, since what you put there is inserted directly into the generated code.
The calculator:
Each argument is assigned to a property of the name in the calculator; that is, the value of the first argument, country, is assigned to country property, and the value of the second one, tariff, to the tariff property. Of course, you can configure values for others calculator properties with <set/> as usual in calculators.
And to use the method:
Methods are the sauce of the objects, without them the object only would be a silly wrapper of data. When possible it is better to put the business logic in methods (model layer) instead of in actions (controller layer).
Finder (8)
A finder is a special method that allows you to find an object or a collection of objects that follow some criteria. In POJO version a finder method is a generated static method in the POJO class. In the EJB2 version a finder matches with a finder in home.
The syntax for finder is:
In this case each time that a DeliveryType is created, just after it, a suffix to description is added.
As you see, this is exactly the same as in other calculators (as calculated properties or method) but is executed just after creation.
Postmodify calculator (11)
With <postmodify-calculator/> you can plug in some logic to execute after the state of the object is changed and just before it is stored in the database, that is, just before executing UPDATE against database.
Its syntax is:
In this case whenever that a DeliveryType is modified a suffix is added to its description.
As you see, this is exactly the same as in other calculators (as calculated properties or methods), but it is executed just after modifying.
Postload and preremove calculator (10, 12)
The syntax and behavior of postload and preremove calculators are the same of the postcreate and and postmodify ones.
Validator (13)
This validator allows to define a validation at model level. When you need to make a validation on several properties at a time, and that validation does not correspond logically with any of them, then you can use this type of validation.
Its syntax is:
class (optional, required if name is not specified): Class that implements the validation logic. It has to be of type IValidator.
name (optional, required if class is not specified): This name is a validator name from xava/validators.xml file of your project or of the OpenXava project.
only-on-create (optional): If true the validator is executed only when creating a new object, not when an existing object is modified. The default value is false.
set (several, optional): To set a value of the validator properties before executing it.
This validator must implement IValidator (1), this forces you to write a validate(Messages messages) (2). In this method you add the error message ids (3) (whose texts are in the i18n files). And if the validation process (that is the execution of all validators) produces some error, then OpenXava does not save the object and displays the errors to the user.
In this case you see how description and unitPrice properties are used to validate, for that reason the validation is at model level and not at individual property level, because the scope of validation is more than one property.
Remove validator (14)
The <remove-validator/> is a level model validator too, but in this case it is executed just before removing an object, and it has the possibility to deny the deletion.
Its syntax is:
class (optional, required if name is not specified): Class that implements the validation logic. Must implement IRemoveValidator.
name (optional, required if class is not specified): This name is a validator name from the xava/validators.xml file of your project or from the OpenXava project.
set (several, optional): To set the value of the validator properties before executing it.
As you see this validator must implement IRemoveValidator (1) this forces you to write a setEntity() (2) method that receives the object to remove. If validation error is added to the Messages object sent to validate() (3) the validation fails. If after executing all validations there are validation errors, then OpenXava does not remove the object and displays a list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery type before deleting it.
name (required): Each aggregate must have a unique name. The rules for this name are the same that for class names in Java, that is, to begin with upper-case and each new word starting with upper-case too.
bean (one, optional): Allows to specify a class written by you to implement the aggregate. The class has to be a JavaBean, that is a plain Java class with getters and setters for properties. Usually this is not used because it is much better that OpenXava generates the code for you.
ejb (one, optional): Allows to use existing EJB2 to implement an aggregate. This can be used only in the case of a collection of aggregates. Usually this is not used because it is much better that OpenXava generates the code for you.
An OpenXava component can have whichever aggregates you want. And you can reference it from the main entity or from another aggregate.
Reference to aggregate
The first example is an aggregate Address that is referenced from the main entity.
In the main entity you can write:
As you see an aggregate can implement an interface (1) and contain references (2), among other things, in fact all thing that you can use in <entity/> are supported in an aggregate.
The resulting code can be used this way, for reading:
ICustomer customer = ...
Address address = customer.getAddress();
address.getStreet();// to obtain the value
Or in this other way to set a new address:
// to set a new address
Address address = new Address();// it's a JavaBean, never an EJB2
address.setStreet(“My street”);
address.setZipCode(46001);
address.setCity(“Valencia”);
address.setState(state);
customer.setAddress(address);
In this case you have a simple reference (not collection), and the generated code is a simple JavaBean, whose life cycle is associated to its container object, that is, the Address is removed and created through the Customer. An Address never will have its own life and cannot be shared by other Customer.
Collection of aggregates
Now an example of a collection of aggregates. In the main entity (for example Invoice) you can write:
As you see an aggregate is as complex as an entity, with calculators, validators, references and so on. In the case of an aggregate used in a collection a reference to the container is added automatically, that is, although you have not defined it, InvoiceDetail has a reference to Invoice.
In the generated code you can find an Invoice with a collection of InvoiceDetail. The difference between a collection of references and a collection of aggregates is that when you remove a Invoice its details are removed too (because they are aggregates). Also there are differences at user interface level (you can learn more on this in chapter 4).
(New in v2.1.1 Reference Guide: Explanation of implicit reference) Every aggregate has a implicit reference to its container model. That is, InvoiceDetail has a reference named invoice, even if the reference is not declared (although it can be optionally declared for refining purpose, for example, for making it key). This reference can be used in Java code, as following:
InvoiceDetail detail = ... ;
detail.getInvoice();// For obtaining the parent
In this case we use invoice in the from attribute (1) although invoice is not declared in InvoiceDetail. New in v2.1.1: using reference to parent model key properties in a from attribute (that is, from="invoice.year")
Many-to-many relationships
In OpenXava XML components there is no direct concept of a many-to-many relationship, only collections are availables. Nevertheless modeling a many-to-many relationship in OpenXava is easy. You only need to define collections in both sides of the relationship.
For example, if you have customers and states, and a customer can work in several states, and, obviously, in a state several customers can work, then you have a many-to-many (using relational nomenclature) relationship. Suppose that you have a table CUSTOMER (without reference to state), a table STATE (without reference to customer) and a table CUSTOMER_STATE (to link both tables). Then you can model this case in this way:
You define in Customer a collection of aggregates (1), each aggregate (CustomerState) (2) contains a reference to a State (4), and, of course, a reference of its container entity (Customer) (3).
Then you map this collection in the usual way:
CustomerState is mapped to CUSTOMER_STATE, a table that only contains two columns, one to link to CUSTOMER (1) and other to link to STATE (2).
At model and mapping level all is right, but the User Interface generated by default by OpenXava is somewhat cumbersome in this case. Although with the next refinements to the view part your many-to-many collection will be just fine:
In this view you can see how we define explicitly (1) the properties to be shown in list of the collection states. This is needed because we have to show the properties of the State, not the CustomerState ones. Additionally, we define that reference to State in CustomerState view to be showed without frames (2), this is to avoid two ugly nested frames.
By this easy way you can define a collection to map a many-to-many relationship in the database. If you want a bidirectional relationship only need to create a customers collection in State entity, this collection may be of the aggregate type StateCustomer and must be mapped to the table CUSTOMER_STATE. All in analogy to the example here.
Table of Contents
Chapter 2: Model XML (classic)
The model layer in an object oriented application contains the business logic, that is the structure of the data and all calculations, validations and processes associated to this data.OpenXava is a model oriented framework where the model is the most important, and the rest (e.g. user interface) depends on it.
The way to define the model in OpenXava is using XML and a few of Java. OpenXava generates a complete Java implementation of your model from your definition.
Java Implementation
Currently OpenXava generates code for the next 4 alternatives:- Plain Java Classes (the so-called POJOs) for the model using Hibernate for persistence.
- Plain Java Classes for the model using EJB3 JPA (Java Persistence API) for persistence (new in v2.1).
- Classic EJB2 EntityBeans for the model and persistence.
- POJOs + Hibernate inside an EJB2 container.
The option 2 is the default one (new in v3.0) and the best for the most cases. The option 1 is also good, specially if you need to use Java 1.4. The option 3 is for supporting all the OpenXava applications written using EJB2 (EJB2 was the only option in OpenXava 1.x). The option 4 can be useful in some circumstances. You can see how to configure this in OpenXavaTest/properties/xava.properties.Business Component
As you have seen the basic unit to create an OpenXava application is the business component. A business component is defined using a XML file. The structure of a business component in OpenXava is:The first part of the component, the part of entity and aggregates, is used to define the model. In this chapter you will learn the complete syntax of this part.
Entity and aggregates
The definition for entity and aggregate are practically identical. The entity is the main object that represents the business concept, while aggregates are additional object needed to define the business concept but cannot have its own life. For example, when you define an Invoice component, the heading data of invoice are in entity, while for invoice lines you can create an aggregate called InvoiceDetail; the life cycle of an invoice line is attached to the invoice, that is an invoice line without invoice has no meaning, and sharing an invoice line by various invoices is not possible, hence you will model InvoiceDetail as aggregate.Formally, the relationship between A and B is aggregation, and B can be modeled as an aggregate when:
- You can say that A has an B.
- If A is deleted then its B is deleted too.
- B is not shared.
Sometimes the same concept can be modeled as aggregate or as entity in another component. For example, the address concept. If the address is shared by various persons then you must use a reference to entity, while if each person has his own address maybe an aggregate is a good option.Entity
The syntax of entity is:Bean
With <bean/> you can specify that you want to use your own Java class.For example:
In this simple way you can write your own Java code instead of using OpenXava to generate it.
For our example you can write a Family class as follows:
If you want a reference from the OpenXava generated code to your own handwritten code, then your Java class has to implement an interface (IFamily in this case) that extends IModel (see org.openxava.test.Family in OpenXavaTest/src).
Additionally you have to define the mapping using Hibernate:
You can put this file in the hibernate folder of your project. Moreover in this folder you have the hibernate.cfg.xml file that you have to edit in this way:
... <session-factory> ... <mapping resource="Family.hbm.xml"/> ... </session-factory> ...In this easy way you can wrap your existing Java and Hibernate code with OpenXava. Of course, if you are creating a new system it is much better to rely on the OpenXava code generation.EJB (2)
With <ejb/> you can specify that you want to use your own EJB (1.1 and 2.x version).For example:
In this simple way you can write you own EJB code instead of using code that OpenXava generates.
You can write the EJB code from scratch (only for genuine men), if you are a normal programmer (hence lazy) probably you prefer to use wizards, or better yet XDoclet. If you choose to use XDoclet, then you can put your own XDoclet classes in the package model (or another package of your choice. This depends on the value of the model.package variable in build.xml) in src folder of your project.; and your XDoclet code will be generated with the rest of OpenXava code.
For our example you can write a FamilyBean class in this way:
On writing your own EJB you must fulfill two little restrictions:
- The class must extend from org.openxava.ejbx.EJBReplicableBase
- It is required at least a ejbCreate (with its ejbPostCreate) that receives as argument a map and assign its values to the bean, as in the example.
Yes, yes, a little intrusive, but are not the EJB the intrusion culmination?Implements (3)
With <implements/> you specify a Java interface that will be implemented by the generated code. Let's see it:And you can write your Java interface in this way:
Beware to make that generated code implements your interface. In this case you have a property named name that generates a method called getName() that implements the interface.
In your generated code you can find an ICustomer interface:
In the POJO generated code you can see:
In the EJB generated code (if you generate it) you can see the remote interface:
and the EJB bean class is affected too
This pithy feature makes the polymorphism a privileged guest of OpenXava.
As you can see OpenXava generates an interface for each component. It's good that in your code you use these interfaces instead of POJO classes or EJB2 remote interfaces. All code made in this way can be used with POJO and EJB2 version on same time, or allows you to migrate from a EJB2 to a POJO version with little effort. Although, if you are using POJOs exclusively you may use the POJOs classes directly and ignore the interfaces, as you wish.
Property (4)
An OpenXava property corresponds exactly to a Java property. It represents the state of an object that can be read and in some cases updated. The object does not have the obligation to store physically the property data, it only must return it when required.The syntax to define a property is:
Stereotype
A stereotype is the way to determine a specific behavior of a type. For example, a name, a comment, a description, etc. all correspond to the Java type java.lang.String but you surely wish validators, default sizes, visual editors, etc. different in each case and you need to tune finer; you can do this assigning a stereotype to each case. That is, you can have the next sterotypes NAME, MEMO or DESCRIPTION and assign them to your properties.OpenXava comes with these generic stereotypes:
- DINERO, MONEY
- FOTO, PHOTO, IMAGEN, IMAGE
- TEXTO_GRANDE, MEMO, TEXT_AREA
- ETIQUETA, LABEL
- ETIQUETA_NEGRITA, BOLD_LABEL
- HORA, TIME
- FECHAHORA, DATETIME
- GALERIA_IMAGENES, IMAGES_GALLERY new in v2.0
- RELLENADO_CON_CEROS, ZEROS_FILLED new in v2.0.2
- TEXTO_HTML, HTML_TEXT (text with editable format) new in v2.0.3
- IMAGE_LABEL, ETIQUETA_IMAGEN (image depending on property content) new in v2.1.5
- EMAIL new in 2.2.3
- TELEFONO, TELEPHONE new in 2.2.3
- WEBURL new in 2.2.3
- IP new in 2.2.4
- ISBN new in 2.2.4
- TARJETA_CREDITO, CREDIT_CARD new in 2.2.4
- LISTA_EMAIL, EMAIL_LIST new in 2.2.4
Now you will learn how to define your own stereotype. You will create one called PERSON_NAME to represent names of persons.Edit (or create) the file editors.xml in your folder xava. And add:
This way you define the editor to render for editing and displaying properties of stereotype PERSON_NAME.
Also you can edit stereotype-type-default.xml and the line:
<for stereotype="PERSON_NAME" type="String"/>Furthermore it is useful to indicate the default size; you can do this by editing default-size.xml of your project:<for-stereotype name="PERSON_NAME" size="40"/>Thus, if you do not put the size in a property of type PERSON_NAME a value of 40 is assumed.Not so common is changing the validator for required, but if you wish to change it you can do it adding to validators.xml of your project the next definition:
Now everything is ready to define properties of stereotype PERSON_NAME:
<property name="name" stereotype="PERSON_NAME" required="true"/>In this case a value of 40 is assumed as size, String as type and the NotBlankCharacterValidator validator is executed to verify if it is required.IMAGES_GALLERY stereotype (new in v2.0)
If you want that a property of your component hold a gallery of images. You only have to declare your property with the IMAGES_GALLERY stereotype, in this way:<property name="photos" stereotype="IMAGES_GALLERY"/>Furthermore, in the mapping part you have to map your property to a table column suitable to store a String with a length of 32 characters (VARCHAR(32)).And everything is done.
In order to support this stereotype you need to setup the system appropriately for your application.
First, create a table in your database to store the images:
The type of IMAGE column can be a more suitable one for your database to store byte [] (for example LONGVARBINARY) .
The name of the table is arbitrary. You need to specify the table name and schema (new in v2.2.4) name in your configuration file (a .properties file in the root of your OpenXava project). In this way:
If you want to work without schema (new in v3.0.2) you need to put the property image.schema.definition of your configuration to empty string, as following:
And finally you need to define the mapping in your hibernate/hibernate.cfg.xml file, thus:
After this you can use the IMAGES_GALLERY stereotype in all components of your application.
Concurrency and version property (new in v2.2.3)
Concurrency is the ability of the application to allow several users to save data at same time without losing data. OpenXava uses an optimistic concurrency schema. Using optimistic concurrency the records are not locked allowing high concurrency without losing data integrity.For example, if a user A read a record and then a user B read the same record, modify it and save the changes, when the user A try to save the record he receives an error, then he need to refresh the data and retry his modification.
For activating concurrency support for an OpenXava component you only need to declare a property using version=”true”, in this way:
<property name="version" type="int" version="true"/>This property is for use of persistence engine (Hibernate or JPA), your application or your user must not use this property directly.Valid values
The element <valid-values/> allows you to define a property that can hold one of the indicated values only. Something like a C (or Java 5) enum.It's easy to use, let's see this example:
The distance property only can take the following values: local, national or international, and as you have not put required=”true” the blank value is allowed too. The type is not necessary, int is assumed.
At user interface level the current implementation uses a combo. The label for each value is obtained from the i18n files.
At Java generated code level creates a distance property of type int that can take the values 0 (no value), 1 (local), 2 (national) o 3 (international).
At database level the value is by default saved as an integer, but you can configure easily to use another type and work with no problem with legate databases. See more at object/relational mapping chapter.
Calculator
A calculator implements the logic to execute when the getter method of a calculated property is called. The calculated properties are read only (only have getter) and not persistent (they do not match with any column of database table).A calculated property is defined in this way:
Now when you (or OpenXava to fill the user interface) call to getUnitPriceInPesetas() the system executes EurosToPesetasCalculator calculator, but before this it sets the value of the property euros of EurosToPesetasCalculator with the value obtained from unitPrice of the current object.
Seeing the calculator code may be instructive:
You can notice two things, first (1) a calculator must implement org.openxava.calculators.ICalculator, and (2) the method calculate() executes the logic to generate the value returned by the property.
According to the above definitions now you can use the generated code in this way:
And result will hold 332.772.
You can define a calculator without set from to define values for properties, as shown below:
In this case the property year and number of DetailsCountCalculator calculator are filled from properties of same name from the current object.
The from attribute supports qualified properties, as following:
The property string4 (1) of the calculator is filled using the value of name of the state (that is a reference), this a qualified property (reference.property). In the case of int5 (2) you can see that customer reference is not declared in Address, because it is referenced from the Customer entity, therefore Address has an implicit reference to its container model (its parent) that you can use in from attribute (new in v2.0.4). That is, the int5 property is filled with the number of the customer which has this address.
Also it's possible to assign a constant value to a calculator property:
In this case the property separator of ConcatCalculator has a constant value.
Another interesting feature of calculator is that you can access from it to the model object (entity or aggregate) that contains the property that is being calculated:
And the calculator:
This calculator implements IModelCalculator (1) (new in v2.0) and to do this it has a method setModel (2), this method is called before calling the calculate() method and thus allows access to the model object (in this case an invoice) that contains the property inside calculate().
Within the code generated by OpenXava you can find an interface for each business concept that is implemented by the POJO class, the EJB2 remote interface and the EJB2 Bean class. That is for Invoice you have a IInvoice interface implemented by Invoice (POJO class), InvoiceRemote (EJB2 remote interface) and InvoiceBean (EJB2 bean class), this last two only if you generate EJB2 code. In the calculator of type IModelCalculator it is advisable to cast to this interface, because in this cases the same calculator works with POJOs, EJB2 remote interface and EJB2 bean class. If you are developing with a POJO only version (maybe the normal case) you can cast directly to the POJO class, in this case Invoice.
This calculator type is less reusable than that which receives simple properties, but sometimes are useful. Why is it less reusable? For example, if you use IInvoice to calculate a discount, this calculator only could be applied to invoices, but if you uses a calculator that receives amount and discountPercentage as simple properties this last calculator could be applied to invoices, deliveries, orders, etc.
From a calculator you have direct access to JDBC connections, here is an example:
And the calculator class:
To use JDBC your calculator must implement IJDBCCalculator (1) and then it will receive a IConnectionProvider (2) that you can use within calculate(). Yes, the JDBC code is ugly and awkward, but sometime it can help to solve performance problems.
The calculators allow you to insert your custom logic in a system where all code is generated; and as you see it promotes the creation of reusable code because the calculators nature (simple and configurable) allows you to use them time after time to define calculated properties and methods. This philosophy, simple and configurable classes that can be plugged in several places is the cornerstone that sustains all OpenXava framework.
OpenXava comes with a set of predefined calculators, you can find them in org.openxava.calculators.
Default value calculator
With <default-value-calculator/> you can associate logic to a property, but in this case the property is readable, writable and persistent. This calculator is for calculating its initial value. For example:In this case when the user tries to create a new Invoice (for example) he will find that the year field already has a value, that he can change if he wants to do.
You can indicate that the value will be calculated just before creating (inserting into database) an object for the first time; this is done this way:
If you use on-create=”true” then you will obtain that effect.
A typical use of the on-create=”true” is for generating identifiers automatically. In the above example, an unique identifier of type String and 32 characters is generated. Also you can use other generation techniques, for example, a database sequence can be defined in this way:
Or maybe you want to use an identity (auto increment) column as key:
SequenceCalculator (new in v2.0.1) and IdentityCalculator (new in v2.0.2) do not work with EJB2. They work with Hibernate and EJB3.
If you define a hidden key property with no default calculator with on-create=”true” then it uses identity, sequence or hilo techniques automatically depending upon the capabilities of the underlying database. As this:
<property name="oid" type="int" hidden="true" key="true"/>Also, this only works with Hibernate and EJB3, not in EJB2.All others issues about <default-value-calculator/> are as in <calculator/>.
Validator
The validator execute validation logic on the value assigned to the property just before storing. A property may have several validators.The technique to configure the validator (with <set/>) is exactly the same than in calculators. With the attribute only-on-create="true" you can define that the validation will be executed only when the object is created, and not when it is modified.
The validator code is:
A validator has to implement IPropertyValidator (1), this obliges to the calculator to have a validate() method where the validation of property is executed. The arguments of validate() method are:
- (2) Messages errors: A object of type Messages that represents a set of messages (like a smart collection) and where you can add the validation errors that you find.
- (3) Object value: The value to validate.
- (4) String objectName: Object name of the container of the property to validate. Useful to use in error messages.
- (5) String propertyName: Name of the property to validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with errors.add()) by sending a message identifier and the arguments. If you want to obtain a significant message you need to add to your i18n file the next entry:exclude_string={0} cannot contain {2} in {1}If the identifier sent is not found in the resource file, this identifier is shown as is; but the recommended way is always to use identifiers of resource files.The validation is successful if no messages are added and fails if messages are added. OpenXava collects all messages of all validators before saving and if there are messages, then it display them and does not save the object.
The package org.openxava.validators contains some common validators.
Default validator (new in v2.0.3)
You can define a default validator for properties depending of its type or stereotype. In order to do it you have to use the file xava/validators.xml of your project to define in it the default validators.For example, you can define in your xava/validators.xml the following:
In this case you are associating the validator PersonNameValidator to the stereotype PERSON_NAME. Now if you define a property as the next one:
<property name="name" stereotype="PERSON_NAME" required="true"/>This property will be validated using PersonNameValidator although the property itself does not define any validator. PersonNameValidator is applied to all properties with PERSON_NAME stereotype.You can also assign a default validator to a type.
In validators.xml files you can also define the validators for determine if a required value is present (executed when you use required="true"). Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining OpenXava/xava/validators.xml and OpenXavaTest/xava/validators.xml.
Calculation (new in v5.7)
With <calculation/> you can define an arithmetic expression to do the calculation for the property. The expression can contain +, -, *, (), numeric values and properties names of the same entity. For example:Note as worker.hourPrice is used to get the value from the reference.
The calculation is executed and displayed when the user changes any value of the properties used in the expression in the user interface, however the value is not saved until the user clicks on save button.
Reference (5)
A reference allows access from an entity or an aggregate to another entity or aggregate. A reference is translated to Java code as a property (with its getter and its setter) whose type is the referenced model Java type. For example a Customer can have a reference to his Seller, and that allows you to write code like this:to access to the name of the seller of that customer.
The syntax of reference is:
- name (optional, required if model is not specified): The name of reference in Java, hence must follow the rules to name members in Java, including start by lower-case. If you do not specify name the model name with the first letter in lower-case is assumed. Using underline (_) is not advisable.
- label (optional): Label shown to the final user. It's much better use i18n.
- model (optional, required if name is not specified): The model name to reference. It can be the name of another component, in which case it is a reference to entity, or the name of a aggregate of the current component. If you do not specify model the reference name with the first letter in upper-case is assumed.
- required (optional): Indicates if the reference is required. When saving OpenXava verifies if the required references are present, if not the saving is aborted and a list of validation errors is returned.
- key (optional): Indicates if the reference is part of the key. The combination of key properties and reference properties should map to a group of database columns with unique values, typically the primary key.
- search-key (optional): (New in v3.0.2) The search key references are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the key (key=”true”) members for searching by default, and if the key (key=”true”) members are hidden then it uses the first property in the view. With search-key you can choose explicitly references for searching.
- role (optional): Used only in references within collections. See below.
- default-value-calculator (one, optional): Implements the logic for calculating the initial value of the reference. This calculator must return the key value, that can be a simple value (only if the key of referenced object is simple) or key object (a special object that wraps the key and is generated by OpenXava).
A little example of references use:- A reference to an aggregate called Address, the reference name will be address.
- A reference to the entity of Seller component. The model is deduced from name.
- A reference called alternateSeller to the entity of component Seller.
If you assume that this is in a component named Customer, you could write:Default value calculator in references
In a reference <defaut-value-calculator/> works like in a property, only that it has to return the value of the reference key, and on-create="true" is not allowed.For example, in the case of a reference with simple key, you can write:
The calculate() method is:
As you can see an integer is returned, that is, the default value for family is 2.
In the case of composed key:
And the calculator code:
Returns an object of type Warehouse, (or WarehouseKey if you use only EJB2).
Collection (6)
With <collection/> you define a collection of references to entities or aggregates. This is translated to Java as a property of type java.util.Collection.Here syntax for collection:
- name (required): The collection name in Java, therefore it must follow the rules for name members in Java, including starting with lower-case. Using underline (_) is not advisable.
- label (optional): Label shown to final user. Is much better to use i18n files.
- minimum (optional): Minimum number of expected elements. This is validated just before saving.
- maximum (optional): (new v2.0.3) Maximum number of expected elements.
- reference (required): With the syntax you can see in the previous point.
- condition (optional): Restricts the elements that appear in the collection.
- order (optional): The elements in collections will be in the indicated order.
- calculator (optional): Allows you to define your own logic to generate the collection. If you use this, then you cannot use neither condition nor order.
- postremove-calculator (optional): Execute your custom logic just after an element is removed from collection.
Let's have a look at some examples. First a simple one:If you have this within an Invoice, then you are defining a deliveries collection associated to that Invoice. The details to make the relationship are defined in the object/relational mapping.
Now you can write a code like this:
To do something with all deliveries associated to an invoice.
Let's look at another example a little more complex, but still in Invoice:
In this case you have a collection of aggregates, the details (or lines) of the invoice. The main difference between collection of entities and collection of aggregates is when you remove the main entity; in the case of a collection of aggregates its elements are deleted too. That is when you delete an invoice its details are deleted too.
- The restriction minimum="1" requires at least one detail for the invoice to be valid.
- With order you force that the details will be returned ordered by serviceType.
- With postremove-calculator you indicate the logic to execute just after a invoice detail is removed.
Let's look at the calculator code:As you see this is a conventional calculator as it is used in calculated properties. A thing to consider is that the calculator is applied to the container entity (in this case Invoice) and not to the collection element. That is, if your calculator implements IModelCalculator then it receives an Invoice and not an InvoiceDetail. This is consistent because it is executed after the detail is removed and the detail doesn't exist any more.
You have full freedom to define how the collection data is obtained, with condition you can overwrite the default condition generated by OpenXava:
If you have this collection within Carrier, you can obtain with this collection all carriers of the same warehouse but not himself, that is the list of his fellow workers. As you see you can use this in the condition in order to reference the value of a property of current object.
If with this you have not enough, you can write the logic that returns the collection. The previous example can be written in the following way too:
And here the calculator code:
As you see this is a conventional calculator. Obviously it must return a java.util.Collection whose elements are of type ICarrier.
The references in collections are bidirectional, this means that if in a Seller you have a customers collection, then in Customer you must have a reference to Seller. But if in Customer you have more than one reference to Seller (for example, seller and alternateSeller) OpenXava does not know which to choose, for this case you have the attribute role of reference. You can use it in this way:
To indicate that the reference seller and not alternateSeller will be used in this collection.
In the case of a collection of entity references you have to define the reference at the other side, but in the case of a collection of aggregate references this is not necessary, because in the aggregates a reference to this container is automatically generated.
Method (7)
With <method/> you can define a method that will be included in the generated code as a Java method.The syntax for method is:
- name (required): Name of the method in Java, therefore it must follow the Java rules to name members, like beginning with lower-case.
- type (optional, by default void): Is the Java type that the method returns. All Java types valid as return type for a Java method are applicable here.
- arguments (optional): Argument list of the method in Java format.
- exceptions (optional): Exception list that can be thrown by this method, in Java format.
- calculator (required): Implements the logic of the method.
Defining a method is easy:And the implementation depends on the logic that you want to program. In this case:
All applicable things for calculators in properties are applicable to methods too, with the next clarifications:
- A calculator for a method has moral authority to change the state of the object.
- If the return type of the method is void the calculator must return null.
Now you can use the method in the expected way:And in newPrice you have 102.
Another example, now a little bit more complex:
In this case you can notice that in arguments and exceptions the Java format is used, since what you put there is inserted directly into the generated code.
The calculator:
Each argument is assigned to a property of the name in the calculator; that is, the value of the first argument, country, is assigned to country property, and the value of the second one, tariff, to the tariff property. Of course, you can configure values for others calculator properties with <set/> as usual in calculators.
And to use the method:
Methods are the sauce of the objects, without them the object only would be a silly wrapper of data. When possible it is better to put the business logic in methods (model layer) instead of in actions (controller layer).
Finder (8)
A finder is a special method that allows you to find an object or a collection of objects that follow some criteria. In POJO version a finder method is a generated static method in the POJO class. In the EJB2 version a finder matches with a finder in home.The syntax for finder is:
- name (required): Name of the finder method in Java, hence it must follow the Java rules for member naming, e.g. beginning with lower-case.
- arguments (required): Argument list for the method in Java format. It is (most) advisable to use simple data types.
- collection (optional, by default false): Indicates if the result will be a single object or a collection.
- condition (optional): A condition with SQL/EJBQL syntax where you can use the names of properties inside ${}.
- order (optional): An order with SQL/EJBQL syntax where you can use the names of properties inside ${}.
Some examples:This generates a set of finder methods available from POJO class and EJB2 home. This methods can be used this way:
Postcreate calculator (9)
With <postcreate-calculator/> you can plug in your own logic to execute just after creating the object as persistent object.Its syntax is:
- class (required): Calculator class. This calculator must implement ICalculator or some of its children.
- set (several, optional): To set the value of the calculator properties before executing it.
A simple example is:And now the calculator class:
In this case each time that a DeliveryType is created, just after it, a suffix to description is added.
As you see, this is exactly the same as in other calculators (as calculated properties or method) but is executed just after creation.
Postmodify calculator (11)
With <postmodify-calculator/> you can plug in some logic to execute after the state of the object is changed and just before it is stored in the database, that is, just before executing UPDATE against database.Its syntax is:
- class (required): Calculator class. A calculator that implements ICalculator or some of its children.
- set (several, optional): To set the value of the calculator properties before execute it.
A simple example is:And now the calculator class:
In this case whenever that a DeliveryType is modified a suffix is added to its description.
As you see, this is exactly the same as in other calculators (as calculated properties or methods), but it is executed just after modifying.
Postload and preremove calculator (10, 12)
The syntax and behavior of postload and preremove calculators are the same of the postcreate and and postmodify ones.Validator (13)
This validator allows to define a validation at model level. When you need to make a validation on several properties at a time, and that validation does not correspond logically with any of them, then you can use this type of validation.Its syntax is:
- class (optional, required if name is not specified): Class that implements the validation logic. It has to be of type IValidator.
- name (optional, required if class is not specified): This name is a validator name from xava/validators.xml file of your project or of the OpenXava project.
- only-on-create (optional): If true the validator is executed only when creating a new object, not when an existing object is modified. The default value is false.
- set (several, optional): To set a value of the validator properties before executing it.
An example:And the validator code:
This validator must implement IValidator (1), this forces you to write a validate(Messages messages) (2). In this method you add the error message ids (3) (whose texts are in the i18n files). And if the validation process (that is the execution of all validators) produces some error, then OpenXava does not save the object and displays the errors to the user.
In this case you see how description and unitPrice properties are used to validate, for that reason the validation is at model level and not at individual property level, because the scope of validation is more than one property.
Remove validator (14)
The <remove-validator/> is a level model validator too, but in this case it is executed just before removing an object, and it has the possibility to deny the deletion.Its syntax is:
- class (optional, required if name is not specified): Class that implements the validation logic. Must implement IRemoveValidator.
- name (optional, required if class is not specified): This name is a validator name from the xava/validators.xml file of your project or from the OpenXava project.
- set (several, optional): To set the value of the validator properties before executing it.
An example can be:And the validator:
As you see this validator must implement IRemoveValidator (1) this forces you to write a setEntity() (2) method that receives the object to remove. If validation error is added to the Messages object sent to validate() (3) the validation fails. If after executing all validations there are validation errors, then OpenXava does not remove the object and displays a list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery type before deleting it.
Aggregate
The aggregate syntax is:- name (required): Each aggregate must have a unique name. The rules for this name are the same that for class names in Java, that is, to begin with upper-case and each new word starting with upper-case too.
- bean (one, optional): Allows to specify a class written by you to implement the aggregate. The class has to be a JavaBean, that is a plain Java class with getters and setters for properties. Usually this is not used because it is much better that OpenXava generates the code for you.
- ejb (one, optional): Allows to use existing EJB2 to implement an aggregate. This can be used only in the case of a collection of aggregates. Usually this is not used because it is much better that OpenXava generates the code for you.
An OpenXava component can have whichever aggregates you want. And you can reference it from the main entity or from another aggregate.Reference to aggregate
The first example is an aggregate Address that is referenced from the main entity.In the main entity you can write:
<reference name="address" model="Address" required="true"/>And in the component level you define:As you see an aggregate can implement an interface (1) and contain references (2), among other things, in fact all thing that you can use in <entity/> are supported in an aggregate.
The resulting code can be used this way, for reading:
Or in this other way to set a new address:
In this case you have a simple reference (not collection), and the generated code is a simple JavaBean, whose life cycle is associated to its container object, that is, the Address is removed and created through the Customer. An Address never will have its own life and cannot be shared by other Customer.
Collection of aggregates
Now an example of a collection of aggregates. In the main entity (for example Invoice) you can write:And define the InvoiceDetail aggregate:
As you see an aggregate is as complex as an entity, with calculators, validators, references and so on. In the case of an aggregate used in a collection a reference to the container is added automatically, that is, although you have not defined it, InvoiceDetail has a reference to Invoice.
In the generated code you can find an Invoice with a collection of InvoiceDetail. The difference between a collection of references and a collection of aggregates is that when you remove a Invoice its details are removed too (because they are aggregates). Also there are differences at user interface level (you can learn more on this in chapter 4).
(New in v2.1.1 Reference Guide: Explanation of implicit reference) Every aggregate has a implicit reference to its container model. That is, InvoiceDetail has a reference named invoice, even if the reference is not declared (although it can be optionally declared for refining purpose, for example, for making it key). This reference can be used in Java code, as following:
Or it can be used in XML code, in this way:
In this case we use invoice in the from attribute (1) although invoice is not declared in InvoiceDetail. New in v2.1.1: using reference to parent model key properties in a from attribute (that is, from="invoice.year")
Many-to-many relationships
In OpenXava XML components there is no direct concept of a many-to-many relationship, only collections are availables. Nevertheless modeling a many-to-many relationship in OpenXava is easy. You only need to define collections in both sides of the relationship.For example, if you have customers and states, and a customer can work in several states, and, obviously, in a state several customers can work, then you have a many-to-many (using relational nomenclature) relationship. Suppose that you have a table CUSTOMER (without reference to state), a table STATE (without reference to customer) and a table CUSTOMER_STATE (to link both tables). Then you can model this case in this way:
You define in Customer a collection of aggregates (1), each aggregate (CustomerState) (2) contains a reference to a State (4), and, of course, a reference of its container entity (Customer) (3).
Then you map this collection in the usual way:
CustomerState is mapped to CUSTOMER_STATE, a table that only contains two columns, one to link to CUSTOMER (1) and other to link to STATE (2).
At model and mapping level all is right, but the User Interface generated by default by OpenXava is somewhat cumbersome in this case. Although with the next refinements to the view part your many-to-many collection will be just fine:
In this view you can see how we define explicitly (1) the properties to be shown in list of the collection states. This is needed because we have to show the properties of the State, not the CustomerState ones. Additionally, we define that reference to State in CustomerState view to be showed without frames (2), this is to avoid two ugly nested frames.
By this easy way you can define a collection to map a many-to-many relationship in the database. If you want a bidirectional relationship only need to create a customers collection in State entity, this collection may be of the aggregate type StateCustomer and must be mapped to the table CUSTOMER_STATE. All in analogy to the example here.